/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package SystemC;

import EventPackage.Event;
import EventPackage.EventManagerInterface;
import EventPackage.EventQueue;
import InstrumentationPackage.MessageWindow;
import SystemC.devices.DeviceIdentification;
import java.util.ArrayList;

/**
 *
 * @author ziza
 */
public class DevicesController {

    private static ArrayList<DeviceIdentification> listOfActiveDevices;
    private static ArrayList<DeviceIdentification> listOfDisableDevices;
    private static InnerWorkerPing innerWorkerPing;

    
    synchronized public static ArrayList<DeviceIdentification> getListOfActiveDevices() {
        return listOfActiveDevices;
    }

    synchronized public static void setListOfActiveDevices(ArrayList<DeviceIdentification> listOfActiveDevices) {
        DevicesController.listOfActiveDevices = listOfActiveDevices;
    }

    synchronized public static ArrayList<DeviceIdentification> getListOfDisableDevices() {
        return listOfDisableDevices;
    }

    synchronized public static void setListOfDisableDevices(ArrayList<DeviceIdentification> listOfDisableDevices) {
        DevicesController.listOfDisableDevices = listOfDisableDevices;
    }
    
    

    public static void main(String args[]) {
        String EvtMgrIP;			// Event Manager IP address
        Event Evt = null;			// Event object
        EventQueue eq = null;			// Message Queue
        EventManagerInterface em = null;	// Interface object to the event manager
        int Delay = 2500;			// The loop delay (2.5 seconds)
        boolean Done = false;			// Loop termination flag
        String message = null;                  // Store the message element
        Long senderId = null;                   // Store the id of the 

        // Initialize the arrays that will store the registed devices and the devices that are not connected
        listOfActiveDevices = new ArrayList<DeviceIdentification>();
        listOfDisableDevices = new ArrayList<DeviceIdentification>();        

        /////////////////////////////////////////////////////////////////////////////////
        // Get the IP address of the event manager
        /////////////////////////////////////////////////////////////////////////////////

        if (args.length == 0 || args.length == 1) {
            // event manager is on the local system

            System.out.println("\n\nAttempting to register on the local machine...");

            try {
                // Here we create an event manager interface object. This assumes
                // that the event manager is on the local machine

                em = new EventManagerInterface();
            } catch (Exception e) {
                System.out.println("Error instantiating event manager interface: " + e);
            } // catch

        } 
        else {

            // event manager is not on the local system

            EvtMgrIP = args[0];

            System.out.println("\n\nAttempting to register on the machine:: " + EvtMgrIP);

            try {
                // Here we create an event manager interface object. This assumes
                // that the event manager is NOT on the local machine

                em = new EventManagerInterface(EvtMgrIP);
            } catch (Exception e) {
                System.out.println("Error instantiating event manager interface: " + e);

            } // catch

        } // if

        // Here we check to see if registration worked. If em is null then the
        // event manager interface was not properly created.

        if (em != null) {
            System.out.println("Registered with the event manager.");

            /*
             * Now we create the humidity control status and message panel * We
             * put this panel about 2/3s the way down the terminal, aligned to
             * the left * of the terminal. The status indicators are placed
             * directly under this panel
             */

            float WinPosX = 0.0f; 	//This is the X position of the message window in terms 
            //of a percentage of the screen height
            float WinPosY = 0.60f;	//This is the Y position of the message window in terms 
            //of a percentage of the screen height 

            MessageWindow mw = new MessageWindow("Devices Controller Status Console", WinPosX, WinPosY);

            // Now we put the indicators directly under the humitity status and control panel

            mw.WriteMessage("Registered with the event manager.");
            
            
            /********************************************************************
             ** Here we start the worker thread to deal with ping
             *********************************************************************/
            
            if (args.length == 0) {
                innerWorkerPing = new InnerWorkerPing("", mw);
            } else {
                innerWorkerPing = new InnerWorkerPing(args[0], mw);
            }
            
            innerWorkerPing.start();

            /********************************************************************
             ** Here we start the main simulation loop
             *********************************************************************/

            while (!Done) {
                try {
                    eq = em.GetEventQueue();

                } // try
                catch (Exception e) {
                    mw.WriteMessage("Error getting event queue::" + e);

                } // catch
                
                
                // Print my arrayfor debug                     
                System.out.println("Active sensors");
                printArray(getListOfActiveDevices());
                System.out.println("\n\nInactive sensors");
                printArray(getListOfDisableDevices());
                

                // If there are messages in the queue, we read through them.
                // We are looking for EventIDs = 4, this is a request to turn the
                // humidifier or dehumidifier on/off. Note that we get all the messages
                // at once... there is a 2.5 second delay between samples,.. so
                // the assumption is that there should only be a message at most.
                // If there are more, it is the last message that will effect the
                // output of the humidity as it would in reality.

                int qlen = eq.GetSize();

                for (int i = 0; i < qlen; i++) {
                    Evt = eq.GetEvent();

                    // Each device registers itself in the DevicesController
                    
                    if (Evt.GetEventId() == 29) {
                        
                        System.out.println("\n\nReceived new Equipment Registration..." );
                        
                        message = Evt.GetMessage();     // Get the sensor description
                        senderId = Evt.GetSenderId();   // Get the sender id
                        
                        if (senderId != null) 
                        {
                            DeviceIdentification deviceIdentification = new DeviceIdentification(senderId, message);
                            getListOfActiveDevices().add(deviceIdentification);
                        } // if
                    } // if
                    
                    // The Maintenence requests the devices that are working
                    
                    if(Evt.GetEventId() == 21) 
                    {
                        System.out.println("\n\nReceived a Request for Working equipments..." );
                        
                        String m = createSensorsList();
                        
                        // Here we create the event and send it.
                        sendEvent(25, m, em);
    
                    } // if
                    
                    // If the event ID == 99 then this is a signal that the simulation
                    // is to end. At this point, the loop termination flag is set to
                    // true and this process unregisters from the event manager.

                    if ( Evt.GetEventId() == 99 )
                    {
                        Done = true;

                        try
                        {
                            em.UnRegister();
                        } // try
                        catch (Exception e)
                        {
                            mw.WriteMessage("Error unregistering: " + e);
                        } // catch

                        mw.WriteMessage( "\n\nSimulation Stopped. \n");

                    } // if

                } // for
                
                // Verify the unactive sensors and notify the user
                
                String unactiveSensors = createUnactiveSensors();
                
                if(unactiveSensors.length() > 0) {
                    
                    System.out.println("\n\nI found new unactive sensors..." );
                    
                    // Here we create the event and send it.
                    sendEvent(26, unactiveSensors, em);
                } // if

                try {
                    Thread.sleep(Delay);
                } // try
                catch (Exception e) {
                    System.out.println("Sleep error:: " + e);
                } // catch

            } // while

        } else {

            System.out.println("Unable to register with the event manager.\n\n");

        } // if

    } // main
    
    
    /**
     * This method is responsible for creating a string with the devices that are working
     * 
     * @return a String with the devices that are active separated by ; 
     */
    private static String createSensorsList() {
        
        String message = "";
        for(DeviceIdentification device: listOfActiveDevices) {
            message = message.concat(device.toString());
            message = message.concat(";");
        } // for
        
        return message;
    }
    
    
    /**
     * This method is responsible for creating a message with the sensors that 
     * did not respond to the ping. The worker thread in this class manages the 
     * arrays that contain the devices that work and the devices that do not 
     * respond to the PING.
     * 
     * @return a string with the description and id of the devices that did not 
     * work separated by a ;
     */
    private static String createUnactiveSensors() {
        
        String message = "";
        DeviceIdentification device = null;
        for(int i = 0; i< getListOfDisableDevices().size(); i++) {
            device = getListOfDisableDevices().remove(i);
            message = message.concat(device.toString());
            message = message.concat(";");
        }
        return message;
    }
    
    
    /**
     * This method is responsible for sending events to the EventManager
     * 
     * @param id and Integer with the event id to be identified by the 
     * @param message String that contains the message to be send through the EventManager
     * @param em EventManagerInterface that allows to deploy a message to the EventManager
     */
    private static void sendEvent(int id, String message, EventManagerInterface em) {
        
        Event evt = new Event( id, message );

        // Here we send the event to the event manager.

        try
        {
            System.out.println("\n I AM SENDING AN EVENT TYPE: " + id + "WITH THE MESSAGE: " + message);
            em.SendEvent( evt );
            System.out.println("Message send:: ");
        } // try

        catch (Exception e)
        {
            System.out.println("Error Confirming Message:: " + e);
        } // catch  
    }
    
    
    private static void printArray(ArrayList<DeviceIdentification> array) {
        
        for(DeviceIdentification dI: array) {
            System.out.println(dI.toString());
        }
    }
    
    
    /*
     * -----------------------------------------------------------------------------------------
     * 
     *              PING WORKER THREAD
     * 
     * -----------------------------------------------------------------------------------------
     */
    
    
    private static class InnerWorkerPing extends Thread {
        
        private static final String PING = "PING";  // PING standard message
        
        private Boolean working;                    // Boolean to stop a thread 
        private String ip;                          // IP address where the event manager is running
        private EventManagerInterface em = null;    // Interface object to the event manager
        private EventQueue eq = null;               // Message Queue
        private Event Evt = null;                   // Event object
        private String EvtMgrIP;                    // Event Manager IP address
        private MessageWindow mw;                   // We use the same window as the Devices controller
        
		
        /**
         * This is the constructor of the working thread that is responsible for 
         * managing the PING_ACK 
         * 
         * @param ip String with the IP address of the EventManager
         */
        InnerWorkerPing(String ip, MessageWindow mw) {
            super();
            this.working = true;
            this.ip = ip;
            this.mw = mw;
            setUpEnvironment();
        }
        

        public Boolean getWorking() {
            return working;
        }

        public void setWorking(Boolean working) {
            this.working = working;
        }
        
        
        /**
         * This method set up the event queue for receiving the events 
         */
        private void setUpEnvironment() {
            if(ip.length() == 0) {
                // event manager is on the local system

                System.out.println("\n\nAttempting to register on the local machine..." );

                try
                {
                    // Here we create an event manager interface object. This assumes
                    // that the event manager is on the local machine

                    em = new EventManagerInterface();
                }

                catch (Exception e)
                {
                    System.out.println("Error instantiating event manager interface: " + e);
                } // catch
            } else {

                // event manager is not on the local system

                EvtMgrIP = ip;

                System.out.println("\n\nAttempting to register on the machine:: " + EvtMgrIP );

                try
                {
                        // Here we create an event manager interface object. This assumes
                        // that the event manager is NOT on the local machine

                        em = new EventManagerInterface( EvtMgrIP );
                }

                catch (Exception e)
                {
                        System.out.println("Error instantiating event manager interface: " + e);
                } // catch

            } // if
            
            if (em != null)
            {
                mw.WriteMessage("Ping Thread registered with the event manager." );
            }
        }
        
        public void run() {
            while (true) {
                try {
                    eq = em.GetEventQueue();
                } catch (Exception ex) {
                    mw.WriteMessage("Error unregistering: " + ex);
                } 
                
                /*
                 * Three events are required to complete the ping cycle
                 * 1. Verify all the elements that respond to the previous ping
                 * 2. Update the array with the elements that respond to the active 
                 * sensors and non active sensors
                 * 3. Send a new ping
                 * 4. Sleep 15 seconds
                 */
                
                int qlen = eq.GetSize();
                
                /*
                 * Copy the elements from the active array to the desabled array 
                 * because whe can copy only the elements that are active and then 
                 * keep the elements that are inactive 
                 */
                
                copyActiveArray();
                clearActiveArray();
                
                System.out.println("\n\nA am the working thread and i am awake..." );
                
                // 1. Verify the elements that respond to the ping event
                
                for ( int i = 0; i < qlen; i++ )
                {
                    Evt = eq.GetEvent();
                    
                    if ( Evt.GetEventId() ==  24)
                    {
                        
                        System.out.println("\n\nReceived Ping Event..." );
                        
                        DeviceIdentification deviceIdentification = createDevice(Evt.GetMessage(), Evt.GetSenderId());
                        
                        //2. Update the active array this means copy the element from the unactive array to the active array
                        
                        updateWorkingArrays(deviceIdentification);
                    }
                }
                
                // 3. Send a new ping
                System.out.println("\n\nSend a new Ping to check equipments..." );
                Event evt = new Event(27, PING );

                // Here we send the event to the event manager.

                try
                {
                    em.SendEvent( evt );
                } // try
                catch (Exception e)
                {
                    System.out.println("Error Confirming Message:: " + e);
                } // catch 

                /*
                 * 4. Sleep after sending the event this way we expect to have ACK events
                 * from all the devices and we do not have to wait for responses
                 */
                
                System.out.println("\n\nNow i will sleep..." );
                
                try {
                    sleep(15000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        
        
        /**
         * This method clear the active devices list in order to allow the new 
         * devices that are active to be copy where. This way we can have only 
         * two arrays to manage all the devices in the system.  
         */
        public void clearActiveArray() {
            getListOfActiveDevices().clear();
        }
        
        
        /**
         * In this method we copy the content of the active devices and in this 
         * new cycle we verify if the device is active or not.
         */
        public void copyActiveArray() {
            
            for(int i = 0; i<getListOfActiveDevices().size(); i++) {
                getListOfDisableDevices().add(getListOfActiveDevices().get(i));
            }
            getListOfDisableDevices().clear();
        }
        
        
        /**
         * This method check if a particular device is active or not. thus the 
         * logic is.
         * 1. Before checking all the elements that are active or not copy the 
         * content of the active devices to the non active devices.
         * 2. Clear the active devices array.
         * 3. Then check if a device that send an ACK is in the non active device.
         * 4. If it is on the non active device move it to the active devices.
         * 5. If not add the device to the active devices
         * 6. If there are devices that are still in the active notify the user 
         * because there are devices that are not workings
         * @param device 
         */
        public void updateWorkingArrays(DeviceIdentification device) {

            for (DeviceIdentification d: getListOfDisableDevices()) {
                if(d.getDeviceId() == device.getDeviceId()) {
                    getListOfDisableDevices().remove(d);
                    
                    if(getListOfActiveDevices().contains(device))
                        getListOfActiveDevices().remove(device);
                    
                    getListOfActiveDevices().add(device);
                    return;
                }
            }
            // If we have a new device 
            getListOfActiveDevices().add(device);
        }
        
        
        /**
         * Create a new device object to add to the array
         * 
         * @param message String with the description of the device
         * @param id Long the identification of the device we assume that its ID 
         * is the devices name.
         * @return DeviceIdentification that contains the id and the description 
         * of the new device.
         */
        public DeviceIdentification createDevice(String message, Long id) {
            return (new DeviceIdentification(id, message));
        }
    }
}
